﻿<h2>Event handlers</h2>
<p>These errors will be caught by the closest error boundary ancestor.</p>
<button id="event-sync" @onclick="@EventHandlerErrorSync">Synchronous</button>
<button id="event-async" @onclick="@EventHandlerErrorAsync">Asynchronous</button>

<hr />
<h2>Lifecycle methods</h2>
<p>These errors will be caught by the closest error boundary ancestor.</p>
<div><label id="parametersset-sync"><input @bind="throwInOnParametersSet" type="checkbox" /> OnParametersSet (sync)</label></div>
<div><label id="parametersset-async"><input @bind="throwInOnParametersSetAsync" type="checkbox" /> OnParametersSetAsync (async)</label></div>
<div><label id="parametersset-cascade-sync"><input @bind="throwInOnParametersSetViaCascading" type="checkbox" /> OnParametersSet via cascading parameter (sync)</label></div>
<div><label id="parametersset-cascade-async"><input @bind="throwInOnParametersSetAsyncViaCascading" type="checkbox" /> OnParametersSetAsync via cascading parameter (async)</label></div>
<div><label id="afterrender-sync"><input @bind="throwInOnAfterRender" type="checkbox" /> OnAfterRender (sync)</label></div>
<div><label id="afterrender-async"><input @bind="throwInOnAfterRenderAsync" type="checkbox" /> OnAfterRenderAsync (async)</label></div>

<CascadingValue Name="@nameof(ErrorCausingChild.ThrowOnCascadingParameterNotification)"
                Value="@throwInOnParametersSetViaCascading">
    <CascadingValue Name="@nameof(ErrorCausingChild.ThrowOnCascadingParameterNotificationAsync)"
                    Value="@throwInOnParametersSetAsyncViaCascading">
        <ErrorCausingChild ThrowOnParametersSet="@throwInOnParametersSet"
                           ThrowOnParametersSetAsync="@throwInOnParametersSetAsync"
                           ThrowOnAfterRender="@throwInOnAfterRender"
                           ThrowOnAfterRenderAsync="@throwInOnAfterRenderAsync" />
    </CascadingValue>
</CascadingValue>

<hr />
<h2>Rendering</h2>
<p>These errors will be caught by the closest error boundary ancestor.</p>
<label><input id="while-rendering" @bind="throwWhileRendering" type="checkbox" /> Throw during rendering</label>
@if (throwWhileRendering)
{
    throw new InvalidTimeZoneException($"Exception from {nameof(BuildRenderTree)}");
}

<hr />
<h2>Custom error boundary</h2>
<p>This shows how to create a common custom error UI by subclassing ErrorBoundary.</p>
<div id="custom-error-boundary-test">
    <CustomErrorBoundary @ref="customErrorBoundary">
        <ErrorCausingCounter />
    </CustomErrorBoundary>
    <button class="recover" @onclick="@(() => customErrorBoundary!.Recover())">Recover programmatically</button>
</div>

<hr />
<h2>Custom error boundary that tries to ignore errors</h2>
<p>This shows that, even if a custom error boundary tries to continue rendering in a non-error state after an error, the subtree will be forcibly rebuilt.</p>
<div id="error-ignorer-test">
    <ErrorIgnorer>
        <ErrorCausingCounter />
    </ErrorIgnorer>
</div>

<hr />
<h2>Exception inline in error boundary markup</h2>
<p>This shows that, if an ErrorBoundary itself fails while rendering its own ChildContent, then it can catch its own exception. But if the error comes from the error content, this triggers the "infinite error loop" detection logic and becomes fatal.</p>
<div id="inline-error-test">
    <ErrorBoundary>
        <ChildContent>
            @if (throwInline) { throw new InvalidTimeZoneException("Inline exception"); }
            <p class="normal-content">Hello!</p>
        </ChildContent>
        <ErrorContent>
            @if (throwInErrorContent) { throw new InvalidTimeZoneException("Inline exception in error content"); }
            <p class="error-message">There was an error: @context</p>
        </ErrorContent>
    </ErrorBoundary>
    <button class="throw-in-childcontent" @onclick="@(() => throwInline = true)">Throw in child content</button>
    <button class="throw-in-errorcontent" @onclick="@(() => throwInErrorContent = true)">Throw in error content (causing infinite error loop)</button>
</div>

<hr />
<h2>Errors after disposal</h2>
<p>Long-running tasks could fail after the component has been removed from the tree. We still want these failures to be captured by the error boundary they were inside when the task began, even if that error boundary itself has also since been disposed. Otherwise, error handling would behave differently based on whether the user has navigated away while processing was in flight, which would be very unexpected and hard to handle.</p>
<div id="error-after-disposal-test">
    @if (!disposalTestRemoveErrorBoundary)
    {
        <CustomErrorBoundary>
            @if (!disposalTestRemoveComponent)
            {
                <DelayedErrorCausingChild ThrowOnParametersSetAsync="@disposalTestBeginDelayedError" />
            }
        </CustomErrorBoundary>
    }
    <button class="throw-after-disposing-component" @onclick="@ThrowAfterDisposalOfComponent">Throw after disposal of component</button>
    <button class="throw-after-disposing-errorboundary" @onclick="@ThrowAfterDisposalOfErrorBoundary">Throw after disposal of enclosing error boundary</button>
</div>

<hr />
<h2>Multiple child errors</h2>
<p>If several child components trigger asynchronous errors, the error boundary will receive error notifications even when it's already in an errored state. This needs to behave sensibly.</p>
<div id="multiple-child-errors-test">
    <CustomErrorBoundary>
        <DelayedErrorCausingChild ThrowOnParametersSetAsync="@multipleChildrenBeginDelayedError" />
        <DelayedErrorCausingChild ThrowOnParametersSetAsync="@multipleChildrenBeginDelayedError" />
        <DelayedErrorCausingChild ThrowOnParametersSetAsync="@multipleChildrenBeginDelayedError" />
    </CustomErrorBoundary>
    <button class="throw-in-children" @onclick="@(() => { multipleChildrenBeginDelayedError = true; })">Cause multiple errors</button>
</div>

<hr />
<h2>Dispatch exception to renderer</h2>
<p>Use DispatchExceptionAsync to see if exceptions are correctly dispatched to the renderer.</p>
<div id="exception-dispatch-async">
    <button id="dispatch-sync-exception" @onclick=SyncExceptionDispatch>Cause exception from sync context</button>
    <button id="dispatch-async-exception" @onclick=AsyncExceptionDispatch>Cause exception from async context</button>
</div>

@code {
    private bool throwInOnParametersSet;
    private bool throwInOnParametersSetAsync;
    private bool throwInOnParametersSetViaCascading;
    private bool throwInOnParametersSetAsyncViaCascading;
    private bool throwInOnAfterRender;
    private bool throwInOnAfterRenderAsync;
    private bool throwWhileRendering;
    private bool throwInline;
    private bool throwInErrorContent;
    private CustomErrorBoundary customErrorBoundary;
    private bool disposalTestRemoveComponent;
    private bool disposalTestRemoveErrorBoundary;
    private bool disposalTestBeginDelayedError;
    private bool multipleChildrenBeginDelayedError;

    void EventHandlerErrorSync()
        => throw new InvalidTimeZoneException("Synchronous error from event handler");

    async Task EventHandlerErrorAsync()
    {
        await Task.Yield();
        throw new InvalidTimeZoneException("Asynchronous error from event handler");
    }

    async Task ThrowAfterDisposalOfComponent()
    {
        // Begin an async process that will result in an exception from a child component
        disposalTestBeginDelayedError = true;
        await Task.Yield();

        // Before it completes, dispose that child component
        disposalTestRemoveComponent = true;
    }

    async Task ThrowAfterDisposalOfErrorBoundary()
    {
        // Begin an async process that will result in an exception from a child component
        disposalTestBeginDelayedError = true;
        await Task.Yield();

        // Before it completes, dispose its enclosing error boundary
        disposalTestRemoveErrorBoundary = true;
    }

    async Task SyncExceptionDispatch()
    {
        await DispatchExceptionAsync(new InvalidTimeZoneException("Synchronous exception in SyncExceptionDispatch"));
    }

    async Task AsyncExceptionDispatch()
    {
        await Task.Yield();
        await DispatchExceptionAsync(new InvalidTimeZoneException("Asynchronous exception in AsyncExceptionDispatch"));
    }
}
